/*
* Copyright 2010 Dovado FZ-LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "DovadosNetworkRequest.h"
#import "Alias.h"
#import "Info.h"
#import "CLog.h"
#import "NetworkToken.h"
#import "Group.h"

@implementation DovadosNetworkRequest
@synthesize mHost;
@synthesize mUsername;
@synthesize mPassword;
@synthesize mPort;
@synthesize mCurrentAlias;
@synthesize mCurrentDimValue;
@synthesize mCurrentTokenValue;
@synthesize mGroupsArray;
@synthesize mAliasArray;
@synthesize mInfoArray;

typedef enum olle {
	REQ_USER_WELCOME = 0,
	REQ_USER_LOGIN,
	REQ_USER_PASS,
	REQ_ALIASES,
	REQ_TURN_ON,
	REQ_TURN_OFF,
	REQ_INFO,
	REQ_DIM,
	REQ_GROUPS,
	REQ_GROUPS_ALIASES,
	REQ_SYNC_ALL,
	REQ_EXIT
} networktype_t;

enum network_status {
	STATE_NOT_STARTED =0,
	STATE_OPEN,
	STATE_CLOSED,
	STATE_TIMEOUT
} network_state_t;

networktype_t mNetworkState;



NSInteger mNextRequestType;
NSInteger mTargetRequestType;
NSInteger mCurrentRequestType;

NSInputStream *inputStream;		
NSOutputStream *outputStream;


- (id) initWithHost:(NSString*) host: (NSInteger) port {	
	[super init];	
	
	// Add a http:// prefix if it doesn't exist. 
	if(![host hasPrefix:@"http://"]) {
		NSMutableString *str = [[NSMutableString alloc] init];
		[str appendFormat:@"http://%@", host];
		mHost = str;
	} else {
		mHost = host;
	}
#ifdef LOG_ENABLED
	NSLog(@"HOST= %@", mHost);
#endif
	mPort = port;
	mUsername = nil;
	mPassword = nil;
	
	mCurrentTokenValue = 0;
	return self;
}

- (void) setUserInformation:(NSString*) username: (NSString*) password: (id<DovadoRequestListener>) listener {
	mUsername = username;
	mPassword = password;
	mListener = listener;
}

- (void) setMHost:(NSString *) host {
	// Add a http:// prefix if it doesn't exist. 
	[mHost release];
	if(![host hasPrefix:@"http://"]) {
		NSMutableString *str = [[NSMutableString alloc] init];
		[str appendFormat:@"http://%@", host];
		mHost = str;
	} else {
		mHost = host;
	}
#ifdef LOG_ENABLED
	NSLog(@"HOST= %@", mHost);
#endif
}

- (void) closeConnection {
	
	NSLog(@"Close the connection");
	
	@try {		
		[inputStream close];
		[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
		[inputStream release];
		inputStream = nil;
		
		[outputStream close];
		[outputStream release];
		outputStream = nil;
	}
	@catch (NSException * e) {
		[mListener dovadoRequestFailed:ERROR_UNABLE_TO_CLOSE_CONNECTION :@""];
	}	
}


- (NSString *) readServerResponse {
	NSMutableString *message = [[[NSMutableString alloc] initWithCapacity:1024] autorelease];			
	uint8_t buf[1024];
	unsigned int len;
	
	@try {	
		while (true) {
			len = [inputStream read:buf maxLength:1024];
			NSMutableData *data = [[NSMutableData alloc] initWithBytes:(const void *) buf length:len];
			NSString *str = [[NSString alloc] initWithData: (NSData *)data encoding:NSUTF8StringEncoding];
			[message appendString:str];
			[data release];
			[str release];			
			
			if(len == 0) {
				NSLog(@"Error: nothing to read from the stream, exit");
				return nil;
			}
			
			if([message hasSuffix:@">> "] || [message hasPrefix:@"bye bye"]) {
				//NSLog(@"Detect end of message");					
				break;
			}
			
		}
	}
	@catch (NSException * e) {
		message = nil;
	}
	
	return message;
	
}

- (BOOL) handleServerResponse: (NSString *) message {
	
	if(message == nil) {
		[mListener dovadoRequestFailed:ERROR_UNKNOWN_ERROR :@""];
		return FALSE;
	}
	
	if(mCurrentRequestType == REQ_USER_WELCOME) {
		//NSLog(@"REQ_USER_WELCOME: %@", message);
		
		if([message hasPrefix:@"This is version"]) {
			return TRUE;
			
		} else if([message hasPrefix:@"error: Another active API already running, quitting"]) {
			[mListener dovadoRequestFailed:ERROR_ANOTHER_ACTIVE_API :message];
			return FALSE;
			
		} else {
			[mListener dovadoRequestFailed:ERROR_UNKNOWN_ERROR :message];
			return FALSE;
		}
		
	} else if(mCurrentRequestType == REQ_USER_LOGIN) {
		//NSLog(@"REQ_USER_LOGIN: %@", message);
		
		if([message hasPrefix:[NSString stringWithFormat:@"Hello %@. Give me your password", mUsername]]) {
			return TRUE;
			
		} else {
			[mListener dovadoRequestFailed:ERROR_UNKNOWN_USER :message];
			return FALSE;
		}
		
	} else if(mCurrentRequestType == REQ_USER_PASS) {
		//NSLog(@"REQ_USER_PASS: password: %@ message: %@",mPassword, message);
		
		if([message hasPrefix:@"Access granted"]) {
			return TRUE;
			
		} else if([message hasPrefix:@"Wrong username and/or password"]) {
			[mListener dovadoRequestFailed:ERROR_UNKNOWN_USER :message];
			return FALSE;
			
		} else {
			[mListener dovadoRequestFailed:ERROR_UNKNOWN_ERROR :message];
			return FALSE;
			
		}

	} else if(mCurrentRequestType == REQ_TURN_ON) {
		//NSLog(@"REQ_TURN_ON: %@", message);
		[mListener onoffRequestDone];
		// No message from router, assume that everything is ok.
		return TRUE;
		
	} else if(mCurrentRequestType == REQ_TURN_OFF) {
		//NSLog(@"REQ_TURN_OFF: %@", message);
		[mListener onoffRequestDone];
		// No message from router, assume that everything is ok. 
		return TRUE;
		
	} else if(mCurrentRequestType == REQ_EXIT) {
		//NSLog(@"REQ_EXIT: %@", message);
		mNetworkState = STATE_CLOSED;
		[self closeConnection];
		return TRUE;
		
	} else if(mCurrentRequestType == REQ_ALIASES) {
		//NSLog(@"REQ_ALIASES:");
		if (mAliasArray != NULL) {
			[mAliasArray release];
		}
		
		mAliasArray = [[NSMutableArray alloc] init];
		
		NSArray *aliasItems = [message componentsSeparatedByString:@"\n"];
		int size = [aliasItems count];
		Alias *alias;
		for(int i=0; i<size; i++) {
			NSString *item = [aliasItems objectAtIndex:i];
						
			if([item hasPrefix:@"ALIAS"]){
				NSArray *lineElements = [item componentsSeparatedByString:@"="];
				alias = [[Alias alloc] init];
				alias.mAlias = [lineElements objectAtIndex:1];
				alias.mAlias = [alias.mAlias stringByReplacingOccurrencesOfString:@"'" withString:@""];
			} else if([item hasPrefix:@"PROTOCOL"]) {
				NSArray *lineElements = [item componentsSeparatedByString:@"="];
				alias.mProtocol = [lineElements objectAtIndex:1];
				alias.mProtocol = [alias.mProtocol stringByReplacingOccurrencesOfString:@"'" withString:@""];
				[mAliasArray addObject:alias];
				[alias release];
			}
		}
		
		if(mTargetRequestType == REQ_ALIASES) {
			[mListener aliasRequestDone:mAliasArray];
		} else if(mTargetRequestType == REQ_GROUPS_ALIASES) {
			[mListener groupsAndAliasRequestDone:mGroupsArray :mAliasArray];
		} else if(mTargetRequestType == REQ_SYNC_ALL) {
			[mListener syncAllRequestDone:mGroupsArray :mAliasArray :mInfoArray];
		}
		return TRUE;
		
	} else if(mCurrentRequestType == REQ_INFO) {
		//NSLog(@"REQ_INFO");
		if (mInfoArray != NULL) {
			[mInfoArray release];
		}
				
		mInfoArray = [[NSMutableArray alloc] init];
		
		NSArray *infoItems = [message componentsSeparatedByString:@"\n"];
		int size = [infoItems count];
		for(int i=0; i<size; i++){
			NSString *item = [infoItems objectAtIndex:i];
			NSArray *lineItem = [item componentsSeparatedByString:@"="];
			if([lineItem count] == 1) {
				lineItem = [item componentsSeparatedByString:@":"];
			}
			
			if([lineItem count] > 1) {
				Info *info = [[Info alloc] init];
				info.mName = [lineItem objectAtIndex:0];
				info.mValue = [lineItem objectAtIndex:1];
				[mInfoArray addObject:info];
				[info release];
			}
		}
		
		if(mTargetRequestType != REQ_SYNC_ALL) {
			[mListener infoRequestDone:mInfoArray];
		}
 		
		return TRUE;
		
	} else if((mCurrentRequestType == REQ_GROUPS) || (mTargetRequestType == REQ_GROUPS_ALIASES) || (mTargetRequestType == REQ_SYNC_ALL)) {
		//NSLog(@"REQ_GROUPS");
		
		if (mGroupsArray != NULL) {
			[mGroupsArray release];
		}
		
		mGroupsArray = [[NSMutableArray alloc] init];		
		NSArray *groupsItems = [message componentsSeparatedByString:@"\n"];
		int size = [groupsItems count];
		Group *group;
		for(int i=0; i<size; i++) {
			NSString *item = [groupsItems objectAtIndex:i];
			
			if([item hasPrefix:@"GROUP_ALIAS"]){
				NSArray *lineElements = [item componentsSeparatedByString:@"="];
				group = [[Group alloc] init];
				group.mAlias = [lineElements objectAtIndex:1];
				group.mAlias = [group.mAlias stringByReplacingOccurrencesOfString:@"'" withString:@""];
				
			} else if([item hasPrefix:@"GROUP_PARTICIPANTS"]) {
				NSArray *lineElements = [item componentsSeparatedByString:@"="];
				group.mParticipants = [lineElements objectAtIndex:1];
				group.mParticipants = [group.mParticipants stringByReplacingOccurrencesOfString:@"'" withString:@""];
				[mGroupsArray addObject:group];
				[group release];
			}
		}
		
		if((mTargetRequestType != REQ_GROUPS_ALIASES) && (mTargetRequestType != REQ_SYNC_ALL)) {
			[mListener groupsRequestDone:mGroupsArray];
		}
		return TRUE;
		
		
	} else if(mCurrentRequestType == REQ_DIM) {
		[mListener dimRequestDone];
		// No message from router, assume that everything is ok.
		return TRUE;
	}
	
	return FALSE;
}

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
	
	switch(eventCode) {
			
		case NSStreamEventErrorOccurred: {
			NSLog(@"NSStreamEventErrorOccurred");
			//mNetworkState = STATE_CLOSED;
			[mListener dovadoRequestFailed:ERROR_UNKNOWN_ERROR :@"NSStreamEventErrorOccurred"];
			[self closeConnection];
		}			
		case NSStreamEventOpenCompleted: {
			//NSLog(@"NSStreamEventOpenCompleted");
			if(mNetworkState != STATE_TIMEOUT) {
				mNetworkState = STATE_OPEN;
			}
			break;
		}				
		case NSStreamEventEndEncountered: {
			//NSLog(@"NSStreamEventEndEncountered");
			[self closeConnection];
			break;
		}			
		case NSStreamEventNone: {
			//NSLog(@"NSStreamEventNone");
			break;
		}			
        case NSStreamEventHasSpaceAvailable: {
			//NSLog(@"NSStreamEventHasSpaceAvailable");
            break;			
        }
		case NSStreamEventHasBytesAvailable: {
			//NSLog(@"NSStreamEventHasBytesAvailable");
			
			if((mNetworkState == STATE_CLOSED) || (mNetworkState == STATE_TIMEOUT)) {
				return;
			}
			
			NSString *serverMessage = [self readServerResponse];
			if([self handleServerResponse:serverMessage]) {
				NSString * str = nil;
				
				mCurrentRequestType = mNextRequestType;
				
				if(mNextRequestType == REQ_USER_LOGIN) {
					str = [NSString stringWithFormat:@"user %@\r\n", [mUsername stringByAddingPercentEscapesUsingEncoding: NSISOLatin1StringEncoding]];				
					mNextRequestType = REQ_USER_PASS;
					
				} else if(mNextRequestType == REQ_USER_PASS) {
					NSString *encodedPassword = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
																									(CFStringRef)mPassword,
																									NULL,
																									(CFStringRef)@"!*'();:@&=+$,/?%#[]",
																									kCFStringEncodingISOLatin1);
					str = [NSString stringWithFormat:@"pass %@\r\n", encodedPassword];
					mNextRequestType = mTargetRequestType;
				} else if(mNextRequestType == REQ_ALIASES) {
					str = [NSString stringWithFormat:@"ts aliases\r\n"];
					mNextRequestType = REQ_EXIT;
					
				} else if(mNextRequestType == REQ_TURN_ON) {
					str = [NSString stringWithFormat:@"ts turn %@ on\r\n", mCurrentAlias];						
					mNextRequestType = REQ_EXIT;
					
				} else if(mNextRequestType == REQ_TURN_OFF) {
					str = [NSString stringWithFormat:@"ts turn %@ off\r\n", mCurrentAlias];
					mNextRequestType = REQ_EXIT;
					
				} else if(mNextRequestType == REQ_INFO) {
					str = [NSString stringWithFormat:@"info\r\n"];						
					mNextRequestType = REQ_EXIT;
					
				} else if(mNextRequestType == REQ_DIM) {
					str = [NSString stringWithFormat:@"ts dim %@ %i\r\n", mCurrentAlias, mCurrentDimValue];	
					mNextRequestType = REQ_EXIT;
					
				} else if(mNextRequestType == REQ_GROUPS) {
					str = [NSString stringWithFormat:@"ts groups\r\n"];
					mNextRequestType = REQ_EXIT;

				} else if(mNextRequestType == REQ_GROUPS_ALIASES) {
					str = [NSString stringWithFormat:@"ts groups\r\n"];
					mNextRequestType = REQ_ALIASES;						
						
				} else if(mNextRequestType == REQ_SYNC_ALL) {
					str = [NSString stringWithFormat:@"info\r\n"];
					mCurrentRequestType = REQ_INFO;
					mNextRequestType = REQ_GROUPS_ALIASES;						
					
				} else if(mNextRequestType == REQ_EXIT) {
					str = [NSString stringWithFormat:@"exit\r\n"];						
					mNextRequestType = -1;
					
				} 
				
				// write the request
				if(str != nil) {
					NSLog(@"write message: %@", str);
					const uint8_t * rawstring =	(const uint8_t *)[str UTF8String];
					[outputStream write:rawstring maxLength:strlen((const char *)rawstring)];
				}
			} else {				
				// Error message close the connection to the router
				mCurrentRequestType = REQ_EXIT;
				mNextRequestType = -1;
				NSString *str = @"exit\r\n";
				NSLog(@"write message: %@", str);
				const uint8_t * rawstring =	(const uint8_t *)[str UTF8String];				
				[outputStream write:rawstring maxLength:strlen((const char *)rawstring)];	
			}

			break;
		}		
	}
	
}

- (void) openTimeoutExpired: (NetworkToken*) value {
	if(value.mTokenValue == mCurrentTokenValue) {
		if(mNetworkState == STATE_NOT_STARTED) {
			NSLog(@"A timeout has occured, close the connection");
			mNetworkState = STATE_TIMEOUT;
			[self stream:nil handleEvent:NSStreamEventErrorOccurred];
		}
	}
}



- (request_error_codes_t) openConnection {
	
	if((mHost == nil) || [mHost isEqualToString:@""]) {
		NSLog(@"ILLEGAL_HOST");
		return ILLEGAL_HOST;
	}
	
	if((mUsername == nil) || [mUsername isEqualToString:@""]) {
		NSLog(@"ILLEGAL_USER");
		return ILLEGAL_USER;
	}
	
	if((mPassword == nil) || [mPassword isEqualToString:@""]) {
		NSLog(@"ILLEGAL_PASSWORD");
		return ILLEGAL_PASSWORD;
	}
		
	NSURL *url = [NSURL URLWithString:mHost];
	
	if (!url) {			
		NSLog(@"%@ is not a valid URL", mHost);			
		return ILLEGAL_HOST;			
	}
	
	mNetworkState = STATE_NOT_STARTED;
	mCurrentRequestType = REQ_USER_WELCOME;
	mNextRequestType = REQ_USER_LOGIN;
		
	CFReadStreamRef readStream;		
	CFWriteStreamRef writeStream;		
	CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[url host], mPort, &readStream, &writeStream);
		
	if(readStream && writeStream) {
		
		inputStream = (NSInputStream *)readStream;
		[inputStream retain];
		[inputStream setDelegate:self];
		[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
		[inputStream open];
		
		outputStream = (NSOutputStream *)writeStream;						
		[outputStream retain];		
		[outputStream setDelegate:self];
		//[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];				
		[outputStream open];
		
		// Set a token for this connection to know which connection to timeout if many request are made. 
		mCurrentTokenValue++;
		NetworkToken *token = [[NetworkToken alloc] init];
		token.mTokenValue = mCurrentTokenValue;		
		[self performSelector:@selector(openTimeoutExpired:) withObject:token afterDelay:10];
		[token release];
		
		if (readStream) {
			CFRelease(readStream);
		}
	
		if (writeStream) {
			CFRelease(writeStream);
		}		
		return REQUEST_OK;		
	}
	
	return ILLEGAL_HOST;
	
}

- (request_error_codes_t) requestAliases {	
	mTargetRequestType = REQ_ALIASES;
	return [self openConnection];
}

- (request_error_codes_t) requestGroups {
	mTargetRequestType = REQ_GROUPS;
	return [self openConnection];
}

- (request_error_codes_t) requestGroupsAndAliases {
	mTargetRequestType = REQ_GROUPS_ALIASES;
	return [self openConnection];
}

- (request_error_codes_t) requestSyncAll {
	mTargetRequestType = REQ_SYNC_ALL;
	return [self openConnection];
}

- (request_error_codes_t) requestTurnOn:(NSString*) alias {
	mTargetRequestType = REQ_TURN_ON;
	mCurrentAlias = alias;
	
	if((mCurrentAlias == nil) || [mCurrentAlias isEqualToString:@""]) {
		return ILLEGAL_ALIAS;
	}
	
	return [self openConnection];
}

- (request_error_codes_t) requestTurnOff:(NSString*) alias {
	mTargetRequestType = REQ_TURN_OFF;
	mCurrentAlias = alias;
	
	if((mCurrentAlias == nil) || [mCurrentAlias isEqualToString:@""]) {
		return ILLEGAL_ALIAS;
	}
	
	return [self openConnection];
}

- (request_error_codes_t) requestInfo {
	mTargetRequestType = REQ_INFO;
	return [self openConnection];
}

- (request_error_codes_t) requestDimAlias:(NSString*) alias: (NSInteger) value {	
	mTargetRequestType = REQ_DIM;
	mCurrentAlias = alias;
	mCurrentDimValue = value;
	
	if((mCurrentAlias == nil) || [mCurrentAlias isEqualToString:@""]) {
		return ILLEGAL_ALIAS;
	} else if(mCurrentDimValue < 0.0f || mCurrentDimValue > 100.0f) {
		return ILLEGAL_DIM_VALUE;
	}
	
	return [self openConnection];
	
}

- (void)dealloc {
	[mHost release];
	[mUsername release];
	[mPassword release];
	[mCurrentAlias release];
	[mGroupsArray release];
	[mAliasArray release];
	[mInfoArray release];
	[super dealloc];
}

@end
